#include <stdio.h>
#include <string.h>
#include <errno.h>
#include "err.h"
#include "str.h"
#include "opt.h"

static const struct option_def *
find_long(const struct option_def *opt, const char *str)
{
	const struct option_def *r;
	size_t str_len, opt_len;
	
	str_len = strlen(str);
	for (; opt->type != OPT_END; opt++) {
		if (opt->type == OPT_GROUP) {
			r = find_long(opt->ptr.group->defs, str);
			if (r)
				return r;
		} else if (opt->longname) {
			opt_len = strlen(opt->longname);
			if (str_len >= opt_len &&
			memcmp(opt->longname, str, opt_len) == 0 &&
			(str[opt_len] == '\0' || str[opt_len] == '='))
				return opt;
		}
	}
	return 0;
}

static const struct option_def *
find_short(const struct option_def *opt, const char *str)
{
	const struct option_def *r;

	for (; opt->type != OPT_END; opt++) {
		if (opt->type == OPT_GROUP) {
			r = find_short(opt->ptr.group->defs, str);
			if (r)
				return r;
		} else if (str[0] && opt->shortname == str[0]) {
			return opt;
		}
	}
	return 0;
}
static int handle(const struct option_def *def, const char *arg)
{
	int r = 0;
	errno = 0;
	switch (def->type) {
	case OPT_FLAG:
		*def->ptr.flag = 1;
		break;
	case OPT_NUMBER:
		r = strtoulong(arg, def->ptr.number);
		break;
	case OPT_TIME:
		r = strtotime(arg, def->ptr.time);
		break;
	case OPT_STRING:
		*def->ptr.string = arg;
		break;
	case OPT_FUNC:
		r = def->ptr.func(def, arg);
		break;
	case OPT_GROUP:
	case OPT_END:
	default:
		break;
	}
	if (r < 0 && errno)
		perrmsg(arg);
	return r;
}
int parse_options(int argc, const char *const *argv,
			const struct option_group *og,
			int noptc, const char **noptv)
{
	int i, end, noptnum;
	const char *opt, *arg;
	const struct option_def *def;

	noptnum = 0;
	end = 0;
	for (i = 1; i < argc; i++) {
		opt = argv[i];
		if (end || opt[0] != '-' || opt[1] == '\0') {
			/* "-",  "?..." */
			if (noptnum < noptc)
				noptv[noptnum++] = opt;
			else
				goto extra_operand;
			continue;
		}
		if (strcmp(opt, "--") == 0) {
			end = 1;
			continue;
		}
		/* "-?*" */
		if (opt[1] == '-') {
			/* --?* */
			def = find_long(og->defs, opt + 2);
			if (!def)
				goto unknown;
			arg = 0;
			if (opt[2 + strlen(def->longname)] == '=') {
				if (def->type == OPT_FLAG)
					goto extra_argument;
				arg = opt + 2 + strlen(def->longname) + 1;
			} else if (def->type != OPT_FLAG) {
				arg = argv[i + 1];
				if (!arg)
					goto lack_argument;
				i++;
			}
			if (handle(def, arg) < 0)
				return -1;
			continue;
		}
		for (opt++; *opt; opt++) {
			def = find_short(og->defs, opt);
			if (!def)
				goto unknown;
			if (def->type != OPT_FLAG) {
				arg = opt + 1;
				if (arg[0] == 0) {
					arg = argv[i + 1];
					i++;
				}
				if (arg == 0)
					goto lack_argument;
				if (handle(def, arg) < 0)
					return -1;
				break;
			}
			if (handle(def, 0) < 0)
				return -1;
		}
	}
	return 0;
unknown:
	pmsgmsg("unknown option", opt);
	return -1;
lack_argument:
	pmsgmsg("lacking an argument", opt);
	return -1;	
extra_argument:
	pmsgmsg("extra argument", opt);
	return -1;
extra_operand:
	pmsgmsg("extra operand", opt);
	return -1;
}
static void print_space(unsigned num)
{
	while(num--)
		putchar(' ');
}
static void print_option_def(const struct option_def *def)
{
	char line[6];
	unsigned width, max_width;
	const struct option_def *d;

	max_width = 8;
	for (d = def; d->type != OPT_END; d++) {
		/* "  -?, --long-options=ARG" */
		if (d->type == OPT_GROUP)
			continue;
		width = 6;
		if (d->longname) {
			width += 2 + strlen(d->longname);
			if (d->type != OPT_FLAG)
				width += 1 + strlen(d->argname);
		}
		if (width > max_width)
			max_width = width;
	}
	if (max_width < 32)
		max_width = 32;
	for (d = def; d->type != OPT_END; d++) {
		width = max_width;
		if (d->type == OPT_GROUP) {
			print_options(d->ptr.group);
			continue;
		}
		memset(line, ' ', sizeof(line));
		if (d->shortname) {
			line[2] = '-';
			line[3] = d->shortname;
		}
		if (d->shortname && d->longname)
			line[4] = ',';
		fwrite(line, 1, 6, stdout);
		width -= 6;
		if (d->longname) {
			printf("--%s", d->longname);
			width -= 2 + strlen(d->longname);
			if (d->type != OPT_FLAG) {
				printf("=%s", d->argname);
				width -= 1 + strlen(d->argname);
			}
		}
		if (!d->help) {
			putchar('\n');
			continue;
		}
		print_space(width + 4);
		printf("%s\n", d->help);
	}
}
void print_options(const struct option_group *og)
{
	putchar('\n');
	if (og->name)
		printf("%s:\n", og->name);
	if (og->description)
		printf("%s\n", og->description);
	print_option_def(og->defs);
}
/**
 * open_file() - wrapper to open file with BUFSIZE fully buffered
 * @name: 
 */
static const size_t file_bufsize = 65536;
FILE *open_file(const char *name, char mode)
{
	static int stdio_set[2];
	const char *mode_str;
	FILE *f;
	int fd;

	if (mode == 'r')
		mode_str = "rb";
	else if (mode == 'w')
		mode_str = "wb";
	else
		return 0;

	if (name == 0 || strcmp(name, "-") == 0) {
		if (mode == 'r') {
			name = "stdin";
			f = stdin;
			fd = 0;
		} else {
			name = "stdout";
			f = stdout;
			fd = 1;
		}
		clearerr(f);
		if (!stdio_set[fd]) {
			if (!freopen(0, mode_str, f) ||
			setvbuf(f, 0, _IOFBF, file_bufsize)) {
				perrmsg(name);
				f = 0;
			} else {
				stdio_set[fd] = 1;
			}
		}
	} else {
		f = fopen(name, mode_str);
		if (!f)
			perrmsg(name);
		if (f && setvbuf(f, 0, _IOFBF, file_bufsize)) {
			perrmsg(name);
			fclose(f);
			f = 0;
		}
	}

	return f;
}

FILE *open_tmpfile(const char *name, char mode)
{
	char buf[name ? strlen(name) + 1 + 4 : 1];
	if (name == 0 || strcmp(name, "-") == 0)
		return open_file(name, mode);
	sprintf(buf, "%s.tmp", name);
	return open_file(buf, mode);
}
void remove_tmp(const char *name)
{
	char buf[name ? strlen(name) + 1 + 4 : 1];
	if (name == 0 || strcmp(name, "-") == 0)
		return;
	sprintf(buf, "%s.tmp", name);
	if (remove(buf) < 0)
		diemsg(1, buf);
}
void replace_tmp(const char *name)
{
	char buf[name ? strlen(name) + 1 + 4 : 1];
	if (name == 0 || strcmp(name, "-") == 0)
		return;
	sprintf(buf, "%s.tmp", name);
	if (remove(name) < 0)
		diemsg(1, name);
	if (rename(buf, name) < 0)
		diemsg(1, buf);
}

void close_file(FILE *f)
{
	if (f == stdin || f == stdout)
		return;
	if (fclose(f) == EOF)
		diemsg(1, "fclose() failed miserably.");
}
